// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"bytes"
	"fmt"
	"strings"
	"unicode/utf8"
)

// htmlNospaceEscaper转义以包含在不带引号的属性值中。
func htmlNospaceEscaper(args ...interface{}) string {
	s, t := stringify(args...)
	if t == contentTypeHTML {
		return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
	}
	return htmlReplacer(s, htmlNospaceReplacementTable, false)
}

// attrEscaper转义以包含在引用的属性值中。
func attrEscaper(args ...interface{}) string {
	s, t := stringify(args...)
	if t == contentTypeHTML {
		return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
	}
	return htmlReplacer(s, htmlReplacementTable, true)
}

// rcdataEscaper转义以包含在RCDATA元素体中。
func rcdataEscaper(args ...interface{}) string {
	s, t := stringify(args...)
	if t == contentTypeHTML {
		return htmlReplacer(s, htmlNormReplacementTable, true)
	}
	return htmlReplacer(s, htmlReplacementTable, true)
}

// htmlEscaper转义以包含在HTML文本中。
func htmlEscaper(args ...interface{}) string {
	s, t := stringify(args...)
	if t == contentTypeHTML {
		return s
	}
	return htmlReplacer(s, htmlReplacementTable, true)
}

// htmlReplacementTable包含需要转义的符文
// 位于带引号的属性值内或文本节点中。
var htmlReplacementTable = []string{
	// https:
	// U+0000空解析错误。将U+FFFD替换
	// 字符附加到当前属性的值。
	// “
	// 和类似的
	// https:
	0:    "\uFFFD",
	'"':  "&#34;",
	'&':  "&amp;",
	'\'': "&#39;",
	'+':  "&#43;",
	'<':  "&lt;",
	'>':  "&gt;",
}

// HTMLNORMREPLACENTTABLE类似于HTMLREPLACENTTABLE，但与
// 不带“&”避免对现有实体进行过度编码。
var htmlNormReplacementTable = []string{
	0:    "\uFFFD",
	'"':  "&#34;",
	'\'': "&#39;",
	'+':  "&#43;",
	'<':  "&lt;",
	'>':  "&gt;",
}

// HTMLNOSPACEREPLACENTABLE包含需要转义的符文
// 在一个不带引号的属性值内。
// 转义的符码集是HTML特殊符和
// 通过在浏览器中运行以下JS确定的符码集：
// /<div id=d></div>
// /<script>（函数（）{
// var a=[]，d=document.getElementById（“d”），i，c，s；
// /for（i=0；i<0x10000；++i）{
// c=String.fromCharCode（i）；
// d.innerHTML=“<span title=“+c+”lt”+c+”></span>“
// /s=d.getElementsByTagName（“span”）[0]；
// document.write（a.join（“，”）；
// }）（</script>
var htmlNospaceReplacementTable = []string{
	0:    "&#xfffd;",
	'\t': "&#9;",
	'\n': "&#10;",
	'\v': "&#11;",
	'\f': "&#12;",
	'\r': "&#13;",
	' ':  "&#32;",
	'"':  "&#34;",
	'&':  "&amp;",
	'\'': "&#39;",
	'+':  "&#43;",
	'<':  "&lt;",
	'=':  "&#61;",
	'>':  "&gt;",
	// /属性值中的分析错误（无引号）和
	// 在属性值状态之前。
	// 被IE视为引用字符。
	'`': "&#96;",
}

// htmlNospaceNormReplacementTable类似于htmlNospaceReplacementTable，但
// 没有“&”，以避免对现有实体进行过度编码。
var htmlNospaceNormReplacementTable = []string{
	0:    "&#xfffd;",
	'\t': "&#9;",
	'\n': "&#10;",
	'\v': "&#11;",
	'\f': "&#12;",
	'\r': "&#13;",
	' ':  "&#32;",
	'"':  "&#34;",
	'\'': "&#39;",
	'+':  "&#43;",
	'<':  "&lt;",
	'=':  "&#61;",
	'>':  "&gt;",
	// 属性值中存在解析错误（未引用）和
	// 在属性值状态之前。
	// 被IE视为引用字符。
	'`': "&#96;",
}

// htmlReplacer返回s，并根据替换表
// 替换符文。当坏符文为true时，某些坏符文可以通过unscaped进行处理。
func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
	written, b := 0, new(strings.Builder)
	r, w := rune(0), 0
	for i := 0; i < len(s); i += w {
		// 不能使用'for因为我们需要保留输入符文的宽度
		// 如果我们看到解码错误，输入
		// 宽度将不是utf8.Runelen（r）我们将溢出缓冲区。
		r, w = utf8.DecodeRuneInString(s[i:])
		if int(r) < len(replacementTable) {
			if repl := replacementTable[r]; len(repl) != 0 {
				if written == 0 {
					b.Grow(len(s))
				}
				b.WriteString(s[written:i])
				b.WriteString(repl)
				written = i + w
			}
		} else if badRunes {
			// No-op.
			// IE不允许在不带引号的属性中使用这些范围。
		} else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
			if written == 0 {
				b.Grow(len(s))
			}
			fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
			written = i + w
		}
	}
	if written == 0 {
		return s
	}
	b.WriteString(s[written:])
	return b.String()
}

// stripTags获取HTML片段并仅返回文本内容。
// 例如，`<b>&iexcl；Hi！</b><script><script>`->&iexcl；Hi！`。
func stripTags(html string) string {
	var b bytes.Buffer
	s, c, i, allText := []byte(html), context{}, 0, true
	// 使用transition funcs帮助我们避免损坏
	// /`<div title=“1>2”>`或者'I<3小马！`.
	for i != len(s) {
		if c.delim == delimNone {
			st := c.state
			// 使用RCDATA，而不是解析为JS或CSS样式。
			if c.element != elementNone && !isInTag(st) {
				st = stateRCDATA
			}
			d, nread := transitionFunc[st](c, s[i:])
			i1 := i + nread
			if c.state == stateText || c.state == stateRCDATA {
				// 发出标记或注释开头的文本。
				j := i1
				if d.state != c.state {
					for j1 := j - 1; j1 >= i; j1-- {
						if s[j1] == '<' {
							j = j1
							break
						}
					}
				}
				b.Write(s[i:j])
			} else {
				allText = false
			}
			c, i = d, i1
			continue
		}
		i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
		if i1 < i {
			break
		}
		if c.delim != delimSpaceOrTagEnd {
			// 使用任何引号。
			i1++
		}
		c, i = context{state: stateTag, element: c.element}, i1
	}
	if allText {
		return html
	} else if c.state == stateText || c.state == stateRCDATA {
		b.Write(s[i:])
	}
	return b.String()
}

// htmlNameFilter接受HTML属性或标记名或
// 已知的安全HTML属性的有效部分。
func htmlNameFilter(args ...interface{}) string {
	s, t := stringify(args...)
	if t == contentTypeHTMLAttr {
		return s
	}
	if len(s) == 0 {
		// 避免违反结构保护。
		// /<input checked{{.K}={.V}>.
		// 如果没有此选项，则如果.K为空，则.V是选中的
		// 的值，否则.V是属性
		// named.K.
		return filterFailsafe
	}
	s = strings.ToLower(s)
	if t := attrType(s); t != contentTypePlain {
		// TODO:拆分属性和元素名称部分筛选器，以便我们可以识别已知属性。
		return filterFailsafe
	}
	for _, r := range s {
		switch {
		case '0' <= r && r <= '9':
		case 'a' <= r && r <= 'z':
		default:
			return filterFailsafe
		}
	}
	return s
}

// commentEscaper返回空的无论输入如何。注释内容与任何解析结构或人类可读内容都不对应，因此最简单和最安全的策略是删除插入注释中的内容。无论是否为静态注释内容，此方法都同样有效
// 是否已从模板中删除。
func commentEscaper(args ...interface{}) string {
	return ""
}
